package com.yfqy.app.modules.system.serviceimpl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.yfqy.app.constant.SystemConfigConstants;
import com.yfqy.app.domain.entity.SystemConfig;
import com.yfqy.app.domain.mapper.SystemConfigMapper;
import com.yfqy.app.enums.ConfigKey;
import com.yfqy.app.modules.system.dto.AutoCompleteDTO;
import com.yfqy.app.modules.system.dto.BindMobileRewardDTO;
import com.yfqy.app.modules.system.dto.CancelTimeoutDTO;
import com.yfqy.app.modules.system.dto.CommissionRatioDTO;
import com.yfqy.app.modules.system.dto.DeductionDTO;
import com.yfqy.app.modules.system.dto.OrderRewardDTO;
import com.yfqy.app.modules.system.dto.RegisterRewardDTO;
import com.yfqy.app.modules.system.dto.ShippingFreeDTO;
import com.yfqy.app.modules.system.dto.SystemConfigDTO;
import com.yfqy.app.modules.system.dto.WithdrawDTO;
import com.yfqy.app.modules.system.service.ConfigDescGenerator;
import com.yfqy.app.modules.system.service.SystemConfigService;
import com.yfqy.app.util.JSON;
import jakarta.annotation.PostConstruct;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.BatchResult;
import org.redisson.api.RBatch;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class SystemConfigServiceImpl implements SystemConfigService {

    @Autowired
    private SystemConfigMapper systemConfigMapper;

    @Autowired
    @Qualifier("singleRedisClient")
    private RedissonClient redissonClient;

    // Redis key 前缀
    private static final String CONFIG_CACHE_PREFIX = "system:config:";
    // 使用Map存储配置描述生成器
    private final Map<ConfigKey, ConfigDescGenerator> descGeneratorMap = new EnumMap<>(ConfigKey.class);

    @PostConstruct
    public void init() {
        // 初始化描述生成器
        descGeneratorMap.put(ConfigKey.ORDER_REWARD_RATIO, this::generateOrderRewardDesc);
        descGeneratorMap.put(ConfigKey.DEDUCTION_RULE, this::generateDeductionRuleDesc);
        descGeneratorMap.put(ConfigKey.REGISTER_REWARD, this::generateRegisterRewardDesc);
        descGeneratorMap.put(ConfigKey.BIND_MOBILE_REWARD, this::generateBindMobileRewardDesc);
        descGeneratorMap.put(ConfigKey.CANCEL_TIMEOUT, this::generateCancelTimeoutDesc);
        descGeneratorMap.put(ConfigKey.AUTO_COMPLETE, this::generateAutoCompleteDesc);
        descGeneratorMap.put(ConfigKey.SHIPPING_FEE, this::generateShippingFeeDesc);
        descGeneratorMap.put(ConfigKey.COMMISSION_RATIO, this::generateCommissionRatioDesc);
        descGeneratorMap.put(ConfigKey.WITHDRAW_SETTING, this::generateWithdrawSettingDesc);
    }


    /**
     * 获取配置值（带Redisson缓存）
     */
    public String getConfigValue(String group, String key) {
        String cacheKey = buildCacheKey(group, key);

        try {
            RBucket<String> bucket = redissonClient.getBucket(cacheKey);

            // 1. 先从Redis获取，使用trySet方法避免不必要的网络请求
            String cachedValue = bucket.get();
            if (cachedValue != null) {
                log.debug("从Redisson缓存获取配置: {}={}", cacheKey, cachedValue);
                return cachedValue;
            }

            // 2. Redis中没有，从数据库获取
            String dbValue = getFromDatabase(group, key);

            // 3. 将结果缓存到Redis，设置过期时间
            if (dbValue != null) {
                bucket.set(dbValue);
                log.debug("配置缓存到Redisson: {}", cacheKey);
            }

            return dbValue;

        } catch (Exception e) {
            log.error("获取配置值失败: group={}, key={}", group, key, e);
            // 降级策略：直接查询数据库
            return getFromDatabase(group, key);
        }
    }



    /**
     * 从数据库获取配置值
     */
    private String getFromDatabase(String group, String key) {
        SystemConfigDTO config = systemConfigMapper.getConfigByConfigGroupConfigKey(group, key);
        return config != null ? config.getConfigValue() : null;
    }

    /**
     * 构建Redis缓存key
     */
    private String buildCacheKey(String group, String key) {
        return CONFIG_CACHE_PREFIX + group + ":" + key;
    }


    /**
     * 清空指定配置的缓存
     */
    public void clearCache(String group, String key) {
        try {
            String cacheKey = buildCacheKey(group, key);
            RBucket<String> bucket = redissonClient.getBucket(cacheKey);
            boolean deleted = bucket.delete();
            log.debug("清空配置缓存: {}, 结果: {}", cacheKey, deleted);
        } catch (Exception e) {
            log.error("清空指定缓存失败: group={}, key={}", group, key, e);
        }
    }

    /**
     * 批量获取配置值（使用Redisson的批量操作）
     */
    public Map<String, String> getConfigValues(String group, List<String> keys) {
        Map<String, String> result = new HashMap<>();
        Map<String, String> needLoadFromDb = new HashMap<>();

        // 批量获取缓存
        Map<String, String> cachedValues = batchGetFromCache(group, keys);

        keys.forEach(key -> {
            String cacheKey = buildCacheKey(group, key);
            String cachedValue = cachedValues.get(cacheKey);

            if (cachedValue != null) {
                result.put(key, cachedValue);
            } else {
                needLoadFromDb.put(key, cacheKey);
            }
        });

        // 从数据库加载缺失的配置
        if (!needLoadFromDb.isEmpty()) {
            loadMissingConfigsFromDb(group, needLoadFromDb, result);
        }

        return result;
    }

    /**
     * 批量从缓存获取
     */
    private Map<String, String> batchGetFromCache(String group, List<String> keys) {
        Map<String, String> result = new HashMap<>();

        try {
            RBatch batch = redissonClient.createBatch();

            // 批量创建get操作
            keys.forEach(key -> {
                String cacheKey = buildCacheKey(group, key);
                batch.getBucket(cacheKey).getAsync();
            });

            // 执行批量操作
            BatchResult<?> batchResult = batch.execute();
            List<?> responses = batchResult.getResponses();

            // 处理响应
            for (int i = 0; i < keys.size(); i++) {
                String value = (String) responses.get(i);
                if (value != null) {
                    result.put(buildCacheKey(group, keys.get(i)), value);
                }
            }

        } catch (Exception e) {
            log.warn("批量获取缓存失败", e);
        }

        return result;
    }

    /**
     * 从数据库加载缺失的配置并缓存
     */
    private void loadMissingConfigsFromDb(String group, Map<String, String> needLoadFromDb,
                                          Map<String, String> result) {
        RBatch batch = redissonClient.createBatch();

        needLoadFromDb.forEach((key, cacheKey) -> {
            String dbValue = getFromDatabase(group, key);
            if (dbValue != null) {
                result.put(key, dbValue);
                // 批量设置缓存
                batch.getBucket(cacheKey).setAsync(dbValue);
            }
        });

        // 执行批量缓存操作
        try {
            batch.execute();
        } catch (Exception e) {
            log.warn("批量设置缓存失败", e);
        }
    }

    /**
     * 使用Redisson的分布式锁确保缓存重建的原子性
     */
    public String getConfigValueWithLock(String group, String key) {
        String cacheKey = buildCacheKey(group, key);
        String lockKey = cacheKey + ":lock";

        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 1. 先尝试获取缓存（无锁）
            RBucket<String> bucket = redissonClient.getBucket(cacheKey);
            String cachedValue = bucket.get();
            if (cachedValue != null) {
                return cachedValue;
            }

            // 2. 获取分布式锁（防止缓存击穿）
            if (lock.tryLock(1, 3, TimeUnit.SECONDS)) {
                try {
                    // 双重检查
                    cachedValue = bucket.get();
                    if (cachedValue != null) {
                        return cachedValue;
                    }

                    // 从数据库加载
                    String dbValue = getFromDatabase(group, key);
                    if (dbValue != null) {
                        bucket.set(dbValue);
                    }
                    return dbValue;

                } finally {
                    lock.unlock();
                }
            } else {
                // 获取锁失败，短暂等待后重试或返回null
                Thread.sleep(50);
                return bucket.get();
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("获取分布式锁被中断", e);
            return getFromDatabase(group, key);
        } catch (Exception e) {
            log.error("带锁获取配置值失败", e);
            return getFromDatabase(group, key);
        }
    }

    /**
     * 发布配置变更通知（Pub/Sub）
     */
    public void publishConfigChange(String group, String key) {
        try {
            RTopic topic = redissonClient.getTopic("system:config:change");
            ConfigChangeEvent event = new ConfigChangeEvent(group, key);
            topic.publish(event);
            log.info("发布配置变更通知: {}/{}", group, key);
        } catch (Exception e) {
            log.error("发布配置变更通知失败", e);
        }
    }

    /**
     * 监听配置变更
     */
    @PostConstruct
    public void subscribeConfigChanges() {
        RTopic topic = redissonClient.getTopic("system:config:change");
        topic.addListener(ConfigChangeEvent.class, (channel, event) -> {
            log.info("收到配置变更通知: {}/{}", event.getGroup(), event.getKey());
            // 清除对应的缓存
            clearCache(event.getGroup(), event.getKey());
        });
    }

    /**
     * 配置变更事件
     */
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class ConfigChangeEvent implements Serializable {
        private String group;
        private String key;
    }

    /**
     * 预加载常用配置到缓存
     */
    @PostConstruct
    public void preloadCommonConfigs() {
        try {
            List<SystemConfigDTO> commonConfigs = systemConfigMapper.selectCommonConfigs();
            if (CollectionUtils.isNotEmpty(commonConfigs)) {
                RBatch batch = redissonClient.createBatch();

                commonConfigs.forEach(config -> {
                    String cacheKey = buildCacheKey(config.getConfigGroup(), config.getConfigKey());
                    batch.getBucket(cacheKey).setAsync(config.getConfigValue());
                });

                batch.execute();
                log.info("预加载常用配置完成，数量: {}", commonConfigs.size());
            }
        } catch (Exception e) {
            log.error("预加载配置失败", e);
        }
    }


    private void generateConfigDescription(SystemConfig systemConfig, Object configValueObj) {
        try {
            ConfigKey configKey = ConfigKey.fromValue(systemConfig.getConfigKey());
            ConfigDescGenerator generator = descGeneratorMap.get(configKey);
            if (generator != null) {
                String desc = generator.generateDesc(configValueObj);
                systemConfig.setConfigDesc(desc);
            }
        } catch (IllegalArgumentException e) {
            // 未知的configKey，不设置描述
            log.warn("未知的配置键: {}, 跳过描述生成", systemConfig.getConfigKey());
        }
    }


    // 各个配置的描述生成方法
    private String generateOrderRewardDesc(Object configValueObj) {
        OrderRewardDTO rewardDTO = JSON.convert(configValueObj, OrderRewardDTO.class);
        String ratio = formatPercentage(rewardDTO.getRatio());
        return String.format(SystemConfigConstants.ORDER_REWARD_RATIO_DESC, ratio, rewardDTO.getMax_points());
    }

    private String generateDeductionRuleDesc(Object configValueObj) {
        DeductionDTO deductionDTO = JSON.convert(configValueObj, DeductionDTO.class);
        String deductionRatio = formatPercentage(deductionDTO.getRatio());
        return String.format(SystemConfigConstants.DEDUCTION_RULE_DESC, deductionRatio, deductionDTO.getPoints_per_yuan());
    }

    private String generateRegisterRewardDesc(Object configValueObj) {
        RegisterRewardDTO rewardDTO = JSON.convert(configValueObj, RegisterRewardDTO.class);
        return String.format(SystemConfigConstants.REGISTER_REWARD_DESC, rewardDTO.getPoints());
    }

    private String generateBindMobileRewardDesc(Object configValueObj) {
        BindMobileRewardDTO rewardDTO = JSON.convert(configValueObj, BindMobileRewardDTO.class);
        return String.format(SystemConfigConstants.BIND_MOBILE_REWARD_DESC, rewardDTO.getPoints());
    }

    private String generateCancelTimeoutDesc(Object configValueObj) {
        CancelTimeoutDTO timeoutDTO = JSON.convert(configValueObj, CancelTimeoutDTO.class);
        return String.format(SystemConfigConstants.CANCEL_TIMEOUT_DESC, timeoutDTO.getMinutes());
    }

    private String generateAutoCompleteDesc(Object configValueObj) {
        AutoCompleteDTO completeDTO = JSON.convert(configValueObj, AutoCompleteDTO.class);
        return String.format(SystemConfigConstants.AUTO_COMPLETE_DESC, completeDTO.getDays());
    }

    private String generateShippingFeeDesc(Object configValueObj) {
        ShippingFreeDTO shippingFreeDTO = JSON.convert(configValueObj, ShippingFreeDTO.class);
        return String.format(SystemConfigConstants.SHIPPING_FEE_DESC,
                shippingFreeDTO.getDefault_fee(), shippingFreeDTO.getFree_threshold());
    }

    private String generateCommissionRatioDesc(Object configValueObj) {
        CommissionRatioDTO commissionRatioDTO = JSON.convert(configValueObj, CommissionRatioDTO.class);
        String level1 = formatPercentage(commissionRatioDTO.getLevel1());
        String level2 = formatPercentage(commissionRatioDTO.getLevel2());
        String level3 = formatPercentage(commissionRatioDTO.getLevel3());
        return String.format(SystemConfigConstants.COMMISSION_RATIO_DESC, level1, level2, level3);
    }

    private String generateWithdrawSettingDesc(Object configValueObj) {
        WithdrawDTO withdrawDTO = JSON.convert(configValueObj, WithdrawDTO.class);
        String feeRatio = formatPercentage(withdrawDTO.getFee_ratio());
        return String.format(SystemConfigConstants.WITHDRAW_SETTING_DESC, withdrawDTO.getMin_amount(), feeRatio);
    }

    // 工具方法：格式化百分比
    private String formatPercentage(BigDecimal decimal) {
        return decimal.multiply(BigDecimal.valueOf(100)).setScale(1, RoundingMode.HALF_UP) + "%";
    }
}
